namespace Aphalina.Compression
{
    using System;
    using System.IO;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Text;

    public class DecompressStream : Stream
    {
        private HuffmanTree distanceTree;
        private HuffmanTree literalTree;
        private BitReader Reader;
        private MemoryStream resultStream = new MemoryStream();
        private static int[] staticDistanceCodeLength;
        private static int[] staticLiteralCodeLength;
        private Stream stream;

        public DecompressStream(Stream stream, bool isZLib = false)
        {
            if (!stream.CanRead)
            {
                throw new InvalidOperationException("CantReadStream");
            }
            this.stream = stream;
            this.IsZLib = isZLib;
            this.Reader = new BitReader(stream);
            this.Decode();
        }

        private void BuildStaticTree()
        {
            if (staticLiteralCodeLength == null)
            {
                int[] numArray = new int[0x120];
                for (int i = 0; i < 0x90; i++)
                {
                    numArray[i] = 8;
                }
                for (int j = 0x90; j < 0x100; j++)
                {
                    numArray[j] = 9;
                }
                for (int k = 0x100; k < 280; k++)
                {
                    numArray[k] = 7;
                }
                for (int m = 280; m < 0x120; m++)
                {
                    numArray[m] = 8;
                }
                int[] numArray2 = new int[0x20];
                for (int n = 0; n < 0x20; n++)
                {
                    numArray2[n] = 5;
                }
                staticDistanceCodeLength = numArray2;
                staticLiteralCodeLength = numArray;
            }
            this.literalTree = new HuffmanTree(this.Reader, staticLiteralCodeLength);
            this.distanceTree = new HuffmanTree(this.Reader, staticDistanceCodeLength);
        }

        public void Decode()
        {
            if (this.IsZLib)
            {
                this.Reader.ReadBits(0x10);
            }
            bool flag = false;
            do
            {
                flag = this.Reader.ReadBits(1) == 1;
                switch (this.Reader.ReadBits(2))
                {
                    case 0:
                    {
                        this.Reader.AlignToByte();
                        ushort length = (ushort) this.Reader.ReadBits(0x10);
                        ushort num3 = (ushort) this.Reader.ReadBits(0x10);
                        if (((short) length) != ((short) ~num3))
                        {
                            throw new Exception("InvalidBlockLength");
                        }
                        this.Reader.CopyTo(this.resultStream, length);
                        break;
                    }
                    case 1:
                        this.BuildStaticTree();
                        this.DecodeBlock();
                        break;

                    case 2:
                        this.DecodeDynamicHeader();
                        this.DecodeBlock();
                        break;

                    default:
                        throw new Exception("InvalidBlock");
                }
            }
            while (!flag);
            if (this.IsZLib)
            {
                Adler32 adler = new Adler32();
                adler.Update(this.resultStream.GetBuffer(), 0, (int) this.resultStream.Length);
                this.Reader.AlignToByte();
                if (((this.Reader.ReadBits(8) != ((adler.Value >> 0x18) & 0xff)) || (this.Reader.ReadBits(8) != ((adler.Value >> 0x10) & 0xff))) || ((this.Reader.ReadBits(8) != ((adler.Value >> 8) & 0xff)) || (this.Reader.ReadBits(8) != (adler.Value & 0xff))))
                {
                    throw new Exception("InvalidAdler32");
                }
            }
            this.resultStream.Position = 0;
        }

        private void DecodeBlock()
        {
            int num = -1;
            do
            {
                num = this.literalTree.ReadCode();
                if (num < 0x100)
                {
                    this.resultStream.WriteByte((byte) num);
                }
                else if (num > 0x100)
                {
                    int index = num - 0x101;
                    int num3 = DeflateConstants.LiteralLengthBase[index];
                    int length = DeflateConstants.ExtraLengthBits[index];
                    if (length > 0)
                    {
                        num3 += this.Reader.ReadBits(length);
                    }
                    int num5 = this.distanceTree.ReadCode();
                    if (num5 < 4)
                    {
                        num5++;
                    }
                    else
                    {
                        num5 = DeflateConstants.DistancePositionBase[num5] + this.Reader.ReadBits((num5 - 2) >> 1);
                    }
                    long num6 = this.resultStream.Position - num5;
                    long position = this.resultStream.Position;
                    for (int i = 0; i < num3; i++)
                    {
                        this.resultStream.Position = num6;
                        byte num9 = (byte) this.resultStream.ReadByte();
                        this.resultStream.Position = position;
                        this.resultStream.WriteByte(num9);
                        num6++;
                        position++;
                    }
                }
            }
            while (num != 0x100);
        }

        private void DecodeDynamicHeader()
        {
            int count = this.Reader.ReadBits(5) + 0x101;
            int num2 = this.Reader.ReadBits(5) + 1;
            int num3 = this.Reader.ReadBits(4) + 4;
            int[] codeLength = new int[0x13];
            for (int i = 0; i < num3; i++)
            {
                int num5 = this.Reader.ReadBits(3);
                codeLength[DeflateConstants.HuffmanCodeLengthOrder[i]] = num5;
            }
            HuffmanTree tree = new HuffmanTree(this.Reader, codeLength);
            int[] numArray2 = this.ReadCodeLength(tree, count);
            int[] numArray3 = this.ReadCodeLength(tree, num2);
            this.literalTree = new HuffmanTree(this.Reader, numArray2);
            this.distanceTree = new HuffmanTree(this.Reader, numArray3);
        }

        public override void Flush()
        {
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return this.resultStream.Read(buffer, offset, count);
        }

        private int[] ReadCodeLength(HuffmanTree tree, int count)
        {
            int[] numArray = new int[count];
            int index = 0;
            while (index < count)
            {
                int num2 = tree.ReadCode();
                if (num2 < 0x10)
                {
                    numArray[index] = num2;
                    index++;
                }
                else
                {
                    if (num2 == 0x10)
                    {
                        int num3 = tree.Reader.ReadBits(2) + 3;
                        int num4 = 0;
                        while (num4 < num3)
                        {
                            numArray[index] = numArray[index - 1];
                            num4++;
                            index++;
                        }
                    }
                    else if (num2 == 0x11)
                    {
                        int num5 = tree.Reader.ReadBits(3) + 3;
                        int num6 = 0;
                        while (num6 < num5)
                        {
                            numArray[index] = 0;
                            num6++;
                            index++;
                        }
                    }
                    else
                    {
                        if (num2 != 0x12)
                        {
                            throw new Exception("InvalidHuffmanCodeLengths");
                        }
                        int num7 = tree.Reader.ReadBits(7) + 11;
                        int num8 = 0;
                        while (num8 < num7)
                        {
                            numArray[index] = 0;
                            num8++;
                            index++;
                        }
                    }
                    continue;
                }
            }
            return numArray;
        }

        public void SaveTreeLength(string name, int[] CodeLength)
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < CodeLength.Length; i++)
            {
                builder.AppendFormat("{0} - {1}\r\n", new object[] { i, CodeLength[i] });
            }
            File.WriteAllText(name, builder.ToString());
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return this.resultStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            throw new InvalidOperationException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new InvalidOperationException();
        }

        public override bool CanRead
        {
            get
            {
                return true;
            }
        }

        public override bool CanSeek
        {
            get
            {
                return true;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return false;
            }
        }

        public bool IsZLib { get; set; }

        public override long Length
        {
            get
            {
                return this.resultStream.Length;
            }
        }

        public override long Position
        {
            get
            {
                return this.resultStream.Position;
            }
            set
            {
                this.resultStream.Position = value;
            }
        }
    }
}

